using System;
using System.Collections.Generic;
using System.Text;
using H2.DataTypes;
using System.Reflection;
using System.Windows.Forms;
using System.IO;

namespace H2.Sections
{
    public class Tags
    {
        MapStream Map;

        internal List<string> PathCache;
        internal List<string> ClassCache;
        internal List<TagInfo> TagInfoCache;

        internal Dictionary<string, Type> TagStructureTypeDic;

        internal Tags(MapStream map)
        { 
            Map = map;

            CreateStructureCache();
            CreateTagCache();
        }

        private void CreateStructureCache()
        {
            Type[] types = typeof(H2.TagStructures).GetNestedTypes();
            TagStructureTypeDic = new Dictionary<string, Type>(types.Length);

            for (int i = 0; i < types.Length; i++)
            {
                switch (types[i].Name)
                {
                    case "jpt":
                        TagStructureTypeDic.Add("jpt!", types[i]);
                        break;
                    case "sfx":
                        TagStructureTypeDic.Add("sfx+", types[i]);
                        break;
                    case "snd":
                        TagStructureTypeDic.Add("snd!", types[i]);
                        break;
                    case "spk":
                        TagStructureTypeDic.Add("spk!", types[i]);
                        break;
                    case "ugh":
                        TagStructureTypeDic.Add("ugh!", types[i]);
                        break;
                    case "ant":
                        TagStructureTypeDic.Add("ant!", types[i]);
                        break;
                    case "char_":
                        TagStructureTypeDic.Add("char", types[i]);
                        break;
                    case "fx":
                        TagStructureTypeDic.Add("<fx>", types[i]);
                        break;
                    default:
                        TagStructureTypeDic.Add(types[i].Name, types[i]);
                        break;
                }
            }
        }

        private void CreateTagCache()
        {
            long oldpos = Map.Position;

            int count = Map.Header.FileTableCount;

            Map.Position = Map.FileTableOffset;

            PathCache = new List<string>(count);
            ClassCache = new List<string>(count);
            TagInfoCache = new List<TagInfo>(count);

            PathCache.AddRange(new string(Map.ReadChars(Map.Header.FileTableSize)).Split(new char[]{'\0'}, StringSplitOptions.RemoveEmptyEntries));

            int ScnrID = Map.Index.ScnrID;

            Map.Position = Map.TagInfoIndexOffset;

            for (int i = 0; i < count; i++)
            {
                ClassCache.Add(Map.ReadReverseString(4, false));
                
                TagInfoCache.Add(new TagInfo());
                TagInfoCache[i].ID = Map.ReadInt32();
                TagInfoCache[i].ChangeOffset(Map.ReadInt32() - Map.SecondaryMagic, Map.SecondaryMagic);
                TagInfoCache[i].Size = Map.ReadInt32();

                if (TagInfoCache[i].ID == ScnrID) Map.Scnr = TagInfoCache[i];
            }

            Map.Position = oldpos;
        }

        public string[] GetStringArrayOfClasses()
        {
            List<string> classes = new List<string>(ClassCache.Count);
            foreach (string s in ClassCache)
                if (!classes.Contains(s)) classes.Add(s);
            return classes.ToArray();
        }

        public TagTable GetTagTable()
        {
            Map.Status = "Creating Tag Table";
            TagTable tt = new TagTable(ClassCache.Count);
            for (int i = 0; i < ClassCache.Count; i++)
                tt.Add(new TagTable.TagRef(ClassCache[i], PathCache[i], TagInfoCache[i].Offset, TagInfoCache[i].Size, TagInfoCache[i].ID));
            tt.Sort();
            return tt;
        }

        public class TagTable
        {
            internal TagTable(int Capacity)
            { Tags = new List<TagRef>(Capacity); }

            private List<TagRef> Tags;

            public TagRef this[int index] { get { return Tags[index]; } set { Tags[index] = value; } }

            public int Count { get { return Tags.Count; } }

            internal void Add(TagRef tr)
            { Tags.Add(tr); }

            public void Sort()
            { Tags.Sort(); }

            public class TagRef: IComparable<TagRef>
            {
                internal TagRef(string _class, string _path, int _offset, int _size, int _id)
                { Class = _class; Path = _path; Offset = _offset; Size = _size; ID = _id; }

                public string Class;
                public string Path;
                public int Offset;
                public int Size;
                public int ID;

                #region IComparable<TagRef> Members

                public int CompareTo(TagRef other)
                {
                    if (this.Class != other.Class)
                        return this.Class.CompareTo(other.Class);
                    else
                        return this.Path.CompareTo(other.Path);
                }

                #endregion
            }
        }

        public void Flush()
        {
            long oldpos = Map.Position;

            int count = Map.Header.FileTableCount;

            Map.Position = Map.FileTableOffset;

            foreach (string path in PathCache)
            {
                Map.Write(path.ToCharArray());
                Map.Write('0');
            }

            Map.Position = Map.TagInfoIndexOffset;

            for (int i = 0; i < count; i++)
            {
                Map.Write(ClassCache[i].ToCharArray(0, 4));
                Map.Write(TagInfoCache[i].ID);
                Map.Write(TagInfoCache[i].RawMetaOffset);
                Map.Write(TagInfoCache[i].Size);
            }

            Map.Position = oldpos;
        }

        /// <summary>
        /// Gets A Object Of Type TagType. If Type Is Not Found, Returns Null.
        /// </summary>
        /// <param name="map">The MapStream From Which To Retrieve The Tag Object</param>
        /// <param name="TagType">The Type Of Tag To Read, e.g. itmc</param>
        /// <param name="Offset">The Start Offset Of The Tag In The Map Stream</param>
        /// <returns></returns>
        public object LoadTag(int i, SearchType st)
        {
            if (st == SearchType.Index)
            {
                Map.Status = "Loading Tag " + ClassCache[i] + " - " + PathCache[i];
                Application.DoEvents();
                Type t = TagStructureTypeDic[ClassCache[i]];
                if (t == null) return null;
                int magic;
                if (ClassCache[i] == "sbsp") magic = Map.Index.PrimaryMagic;
                else magic = Map.SecondaryMagic;
                return BlockFromStream(Map, TagInfoCache[i].ID, t, magic, true);
            }
            else
            {
                for (int x = 0; x < TagInfoCache.Count; x++)
                {
                    if (TagInfoCache[x].ID == i)
                    {
                        Map.Status = "Loading Tag " + ClassCache[x] + " - " + PathCache[x];
                        Application.DoEvents();
                        Type t = TagStructureTypeDic[ClassCache[x]];
                        if (t == null) return null;
                        int magic;
                        if (ClassCache[x] == "sbsp") magic = Map.Index.PrimaryMagic;
                        else magic = Map.SecondaryMagic;
                        return BlockFromStream(Map, TagInfoCache[x].Offset, t, magic, true);
                    }
                }
            }
            return null;
        }

        public object LoadTag(string Class, string Path)
        {
            Map.Status = "Loading Tag " + Class + " - " + Path;
            Application.DoEvents();
            Type t = TagStructureTypeDic[Class];
            if (t == null) return null;
            int magic;
            if (Class == "sbsp") magic = Map.Sbsp[Path].Magic;
            else magic = Map.SecondaryMagic;
            return BlockFromStream(Map, this[Class, Path].Offset, t, magic, true);
        }

        public TagContainer ExtractTag(int i, SearchType st, BinaryReader MainMenu, BinaryReader Shared, BinaryReader SpShared)
        {
            TagContainer con = new TagContainer();
            con.Tag = LoadTag(i, st);
            con.RawData = GetRaw(con.Tag, con.Tag.GetType().Name, MainMenu, Shared, SpShared);
            return con;
        }

        public TagContainer ExtractTag(string Class, string Path, BinaryReader MainMenu, BinaryReader Shared, BinaryReader SpShared)
        {
            TagContainer con = new TagContainer();
            con.Tag = LoadTag(Class, Path);
            con.RawData = GetRaw(con.Tag, Class, MainMenu, Shared, SpShared);
            return con;
        }

        private object[] GetRaw(object Tag, string Class, BinaryReader MainMenu, BinaryReader Shared, BinaryReader SpShared)
        {
            switch (Class)
            {
                case "bitm":
                    #region bitm
                    TagStructures.bitm._68_bitmap_data[] bitmData = ((TagStructures.bitm)Tag)._68_Bitmap_Data;
                    Map.Status = "Getting Bitmap Data...";
                    H2.Sections.BitmapCollection.BitmapDataChunk[] RawData = new H2.Sections.BitmapCollection.BitmapDataChunk[bitmData.Length];

                    int offset = bitmData[0]._28_LOD1_Offset;
                    BinaryReader map = null;
                    #region SelectMap
                    long Pointer = (int)offset & 0XC0000000;
                    offset &= (int)0X3FFFFFFF;
                    switch (Pointer)
                    {
                        case 0:
                            map = new BinaryReader(Map.BaseStream);
                            break;
                        case 0X80000000:
                            map = Shared;
                            break;
                        case 0XC0000000:
                            map = SpShared;
                            break;
                        case 0X40000000:
                            map = MainMenu;
                            break;
                    }
                    #endregion
                    if (map == null) return new H2.Sections.BitmapCollection.BitmapDataChunk[0];

                    for (int i = 0; i < bitmData.Length; i++)
                    {
                        RawData[i] = new H2.Sections.BitmapCollection.BitmapDataChunk();

                        //Lod 1
                        map.BaseStream.Position = bitmData[i]._28_LOD1_Offset & (int)0X3FFFFFFF;
                        RawData[i].LOD1 = map.ReadBytes(bitmData[i]._52_LOD1_Size);

                        //Lod 2
                        map.BaseStream.Position = bitmData[i]._32_LOD2_Offset & (int)0X3FFFFFFF;
                        RawData[i].LOD2 = map.ReadBytes(bitmData[i]._56_LOD2_Size);

                        //Lod 3
                        map.BaseStream.Position = bitmData[i]._36_LOD3_Offset & (int)0X3FFFFFFF;
                        RawData[i].LOD3 = map.ReadBytes(bitmData[i]._60_LOD3_Size);
                    }
                    #endregion
                    return RawData;
                case "mode":
                    return new object[0];
                case "snd_":
                    return new object[0];
                    //etc remember to use TagStructure names eg snd_
                default:
                    return new object[0];
            }
        }

        internal static object BlockFromStream(MapStream map, int Offset, Type t, int magic, bool retaincurrentposition)
        {
            long pos = map.Position;
            map.Position = (long)Offset;

            object Block = t.InvokeMember(null,
        BindingFlags.DeclaredOnly |
        BindingFlags.Public | BindingFlags.NonPublic |
        BindingFlags.Instance | BindingFlags.CreateInstance, null, null, new object[0]);

            FieldInfo startoffsetfield = t.GetField("__StartOffset__", BindingFlags.Instance | BindingFlags.NonPublic);
            if (startoffsetfield != null) startoffsetfield.SetValue(Block, Offset);

            FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance);

            foreach (FieldInfo f in fields)
            {
                Type FieldType = f.FieldType;

                if (FieldType.IsArray)
                {
                    if (FieldType == typeof(Byte[]))
                        f.SetValue(Block, map.ReadBytes(((byte[])f.GetValue(Block)).Length));
                    else if (FieldType == typeof(Char[]))
                        f.SetValue(Block, map.ReadChars(((char[])f.GetValue(Block)).Length));
                    else
                    {
                        FieldType = FieldType.GetElementType();
                        int len = map.ReadInt32();
                        int startoffset = map.ReadInt32() - magic;
                        Array chunks = Array.CreateInstance(FieldType, len);

                        long currentoffset = map.Position;
                        for (int i = 0; i < chunks.Length; i++)
                        {
                            chunks.SetValue(BlockFromStream(map, startoffset, FieldType, magic, false), i);
                            startoffset = (int)map.Position;
                        }

                        f.SetValue(Block, chunks);

                        map.Position = currentoffset;
                    }
                }
                else
                {
                    if (FieldType == typeof(Byte))
                        f.SetValue(Block, map.ReadByte());
                    else if (FieldType == typeof(Single))
                        f.SetValue(Block, map.ReadFloat());
                    else if (FieldType == typeof(Int16))
                        f.SetValue(Block, map.ReadInt16());
                    else if (FieldType == typeof(Int32))
                        f.SetValue(Block, map.ReadInt32());
                    else if (FieldType == typeof(Int64))
                        f.SetValue(Block, map.ReadInt64());
                    else if (FieldType == typeof(UInt16))
                        f.SetValue(Block, map.ReadUInt16());
                    else if (FieldType == typeof(UInt32))
                        f.SetValue(Block, map.ReadUInt32());
                    else if (FieldType == typeof(UInt64))
                        f.SetValue(Block, map.ReadUInt64());
                    else if (FieldType == typeof(H2.DataTypes.String))
                        f.SetValue(Block, map.ReadFixedLengthString(((H2.DataTypes.String)f.GetValue(Block)).Length, false));
                    else if (FieldType == typeof(Bitmask))
                    {
                        Bitmask Bitmask = (Bitmask)f.GetValue(Block);
                        if (Bitmask.ByteCountLength == 1)
                            Bitmask.SetAllData(map.ReadBytes(1));
                        else if (Bitmask.ByteCountLength == 2)
                            Bitmask.SetAllData(map.ReadBytes(2));
                        else if (Bitmask.ByteCountLength == 4)
                        {
                            byte[] b = map.ReadBytes(4);
                            Bitmask.SetAllData(b);
                        }
                        f.SetValue(Block, Bitmask);
                    }
                    else if (FieldType == typeof(Dependancy))
                        f.SetValue(Block, map.ReadDependancy());
                    else if (FieldType == typeof(StringID))
                    {
                        StringID sid = map.ReadStringID();
                        f.SetValue(Block, sid);
                    }
                    else if (FieldType == typeof(H2.DataTypes.Enum))
                    {
                        H2.DataTypes.Enum Enum = (H2.DataTypes.Enum)f.GetValue(Block);
                        if (Enum.ByteCountLength == 1)
                            Enum.Value = map.ReadByte();
                        else if (Enum.ByteCountLength == 2)
                            Enum.Value = map.ReadInt16();
                        else if (Enum.ByteCountLength == 4)
                            Enum.Value = map.ReadInt32();
                        f.SetValue(Block, Enum);
                    }
                    else
                        throw new Exception("Unknown Type");
                }
            }

            if(retaincurrentposition) map.Position = pos;

            return Block;
        }

        public TagInfo[] GetTagInfoArrayOfClass(string Class)
        {
            int count = TagInfoCache.Count;
            List<TagInfo> tags = new List<TagInfo>();
            for (int i = 0; i < count; i++)
                if (ClassCache[i] == Class) tags.Add(TagInfoCache[i]);

            return tags.ToArray();
        }

        public string[] GetStringArrayOfClass(string Class)
        {
            int count = TagInfoCache.Count;
            List<string> tags = new List<string>();
            for (int i = 0; i < count; i++)
                if (ClassCache[i] == Class) tags.Add(PathCache[i]);
            return tags.ToArray();
        }

        /// <summary>
        /// Returns The TagInfo
        /// </summary>
        /// <param name="i"></param>
        /// <param name="st"></param>
        /// <returns></returns>
        public TagInfo this[int i, SearchType st]
        {
            get
            {
                if (st == SearchType.Index)
                    return TagInfoCache[i];
                else foreach (TagInfo tag in TagInfoCache) if (tag.ID == i) return tag;
                return null;
            }
            set
            {
                if (st == SearchType.Index)
                {
                    TagInfoCache[i] = value;

                    long oldpos = Map.Position;

                    Map.Position = Map.Header.FileTableIndexOffset + (i * 12);

                    Map.Write(TagInfoCache[i].ID);
                    Map.Write(TagInfoCache[i].RawMetaOffset);
                    Map.Write(TagInfoCache[i].Size);

                    Map.Position = oldpos;
                }
                else
                {
                    for (int x = 0; x < TagInfoCache.Count; x++)
                    {
                        if (TagInfoCache[x].ID == i)
                        {
                            TagInfoCache[x] = value;
                            long oldpos = Map.Position;

                            Map.Position = Map.Header.FileTableIndexOffset + (i * 12);

                            Map.Write(TagInfoCache[x].ID);
                            Map.Write(TagInfoCache[x].RawMetaOffset);
                            Map.Write(TagInfoCache[x].Size);

                            Map.Position = oldpos;
                            break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Returns The TagInfo
        /// </summary>
        /// <param name="Class"></param>
        /// <param name="Path"></param>
        /// <returns></returns>
        public TagInfo this[string Class, string Path]
        {
            get
            {
                for (int i = 0; i < PathCache.Count; i++)
                {
                    if (PathCache[i] == Path)
                    {
                        if (ClassCache[i] == Class)
                        { return TagInfoCache[i]; }
                    }
                }
                return null;
            }
            set
            {
                for (int i = 0; i < PathCache.Count; i++)
                {
                    if (PathCache[i] == Path)
                    {
                        if (ClassCache[i] == Class)
                        { TagInfoCache[i] = value; break; }
                    }
                }
            }
        }

        public enum SearchType
        {
            Index,
            ID
        }

        /// <summary>
        /// Gets The Tag Path Of i
        /// </summary>
        /// <param name="i"></param>
        /// <param name="st"></param>
        /// <returns></returns>
        public string GetTagPath(int i, SearchType st)
        {
            if (st == SearchType.Index)
                return PathCache[i];
            for (int x = 0; x < TagInfoCache.Count; x++)
                if (TagInfoCache[x].ID == i)
                    return PathCache[x];
            return "";
        }

        /// <summary>
        /// Changes The Class Of A Tag
        /// </summary>
        /// <param name="Class"></param>
        /// <param name="OldPath"></param>
        /// <param name="NewPath"></param>
        public void ReClass(string OldClass, string Path, string NewClass)
        {
            for (int i = 0; i < PathCache.Count; i++)
            {
                if (PathCache[i] == Path && ClassCache[i] == OldClass)
                {
                    if (NewClass.Length < 4)
                    {
                        for (int x = NewClass.Length; x < 5; x++)
                            NewClass += '\0';
                    }
                    else if (NewClass.Length > 4)
                        NewClass = NewClass.Substring(0, 4);

                    ClassCache[i] = NewClass;

                    long oldpos = Map.Position;

                    Map.Position = Map.TagInfoIndexOffset + 16 * i;

                    Map.WriteReverseString(NewClass);

                    Map.Position = oldpos;
                }
            }
        }

        /// <summary>
        /// Changes The Class Of A Tag
        /// </summary>
        /// <param name="i"></param>
        /// <param name="st"></param>
        /// <param name="newPath"></param>
        public void ReClass(int i, SearchType st, string newClass)
        {
            if (st == SearchType.Index)
            {
                if (newClass.Length < 4)
                {
                    for (int x = newClass.Length; x < 5; x++)
                        newClass += '\0';
                }
                else if (newClass.Length > 4)
                    newClass = newClass.Substring(0, 4);

                ClassCache[i] = newClass;

                long oldpos = Map.Position;

                Map.Position = Map.TagInfoIndexOffset + 16 * i;

                Map.WriteReverseString(newClass);

                Map.Position = oldpos;
            }
            else
            {
                for (int y = 0; y < TagInfoCache.Count; y++)
                {
                    if (TagInfoCache[y].ID == i)
                    {
                        if (newClass.Length < 4)
                        {
                            for (int x = newClass.Length; x < 5; x++)
                                newClass += '\0';
                        }
                        else if (newClass.Length > 4)
                            newClass = newClass.Substring(0, 4);

                        ClassCache[y] = newClass;

                        long oldpos = Map.Position;

                        Map.Position = Map.TagInfoIndexOffset + 16 * y;

                        Map.WriteReverseString(newClass);

                        Map.Position = oldpos;
                    }
                }
            }
        }

        /// <summary>
        /// Renames Chosen Tag, Returns The Written Name
        /// </summary>
        /// <param name="i"></param>
        /// <param name="st"></param>
        /// <param name="newPath"></param>
        public string Rename(string Class, string OldPath, string NewPath)
        {
            for (int i = 0; i < PathCache.Count; i++)
            {
                if (PathCache[i] == OldPath && ClassCache[i] == Class)
                {
                    if (PathCache[i].Length > NewPath.Length)
                    {
                        int bytecount = PathCache[i].Length - NewPath.Length;
                        for (int x = 0; x < bytecount; x++)
                            NewPath += ' ';
                    }
                    else if (PathCache[i].Length < NewPath.Length)
                        NewPath = NewPath.Substring(0, PathCache[i].Length);

                    PathCache[i] = NewPath;

                    long oldpos = Map.Position;

                    Map.Position = Map.FileTableOffset;

                    for (int x = 0; x < i; x++)
                        Map.ReadNullTerminatedString();

                    Map.Write(NewPath.ToCharArray());

                    Map.Position = oldpos;

                    break;
                }
            }

            return NewPath;
        }

        /// <summary>
        /// Renames Chosen Tag, Returns The Written Name
        /// </summary>
        /// <param name="i"></param>
        /// <param name="st"></param>
        /// <param name="newPath"></param>
        public string Rename(int i, SearchType st, string newPath)
        {
            if (st == SearchType.Index)
            {
                if (PathCache[i].Length > newPath.Length)
                {
                    int bytecount = PathCache[i].Length - newPath.Length;
                    for (int x = 0; x < bytecount; x++)
                    { newPath += ' '; }
                }
                else if (PathCache[i].Length < newPath.Length)
                    newPath = newPath.Substring(0, PathCache[i].Length);

                PathCache[i] = newPath;

                long oldpos = Map.Position;

                Map.Position = Map.FileTableOffset;

                for (int x = 0; x < i; x++)
                    Map.ReadNullTerminatedString();

                Map.Write(newPath.ToCharArray());

                Map.Position = oldpos;
            }
            else
            {
                for (int y = 0; y < TagInfoCache.Count; y++)
                {
                    if (TagInfoCache[y].ID == i)
                    {
                        if (PathCache[i].Length > newPath.Length)
                        {
                            int bytecount = PathCache[i].Length - newPath.Length;
                            for (int x = 0; x < bytecount; x++)
                                newPath += ' ';
                        }
                        else if (PathCache[i].Length < newPath.Length)
                            newPath = newPath.Substring(0, PathCache[i].Length);

                        PathCache[i] = newPath;

                        long oldpos = Map.Position;

                        Map.Position = Map.FileTableOffset;

                        for (int x = 0; x < i; x++)
                            Map.ReadNullTerminatedString();

                        Map.Write(newPath.ToCharArray());

                        Map.Position = oldpos;

                        break;
                    }
                }
            }

            return newPath;
        }

        public void Duplicate(string Class, string Path, string NewTagPath)
        {
            TagContainer tc = new TagContainer();
            tc.Tag = LoadTag(Class, Path);
            tc.RawData = GetRaw(tc.Tag, Class, null, null, null);
            Add(tc, Class, NewTagPath);
        }

        public int GetSize(object Tag, bool Recursive)
        {
            int size = 0;
            FieldInfo[] fields = Tag.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);

            foreach (FieldInfo f in fields)
            {
                Type FieldType = f.FieldType;

                if (FieldType.IsArray)
                {
                    if (FieldType == typeof(Byte[]))
                        size += ((byte[])f.GetValue(Tag)).Length;
                    else if (FieldType == typeof(Char[]))
                        size += ((char[])f.GetValue(Tag)).Length;
                    else if (Recursive)
                    {
                        Array chunks = (Array)f.GetValue(Tag);
                        size += 8;
                        for (int i = 0; i < chunks.Length; i++)
                            size += GetSize(chunks.GetValue(i), Recursive);
                    }
                    else
                        size += 8;
                }
                else if (FieldType == typeof(Byte))
                    size += 1;
                else if (FieldType == typeof(Single))
                    size += 4;
                else if (FieldType == typeof(Int16))
                    size += 2;
                else if (FieldType == typeof(Int32))
                    size += 4;
                else if (FieldType == typeof(Int64))
                    size += 8;
                else if (FieldType == typeof(UInt16))
                    size += 2;
                else if (FieldType == typeof(UInt32))
                    size += 4;
                else if (FieldType == typeof(UInt64))
                    size += 8;
                else if (FieldType == typeof(H2.DataTypes.String))
                    size += (int)((H2.DataTypes.String)f.GetValue(Tag)).Length;
                else if (FieldType == typeof(Bitmask))
                    size += ((Bitmask)f.GetValue(Tag)).ByteCountLength;
                else if (FieldType == typeof(Dependancy))
                    size += 8;
                else if (FieldType == typeof(StringID))
                    size += 4;
                else if (FieldType == typeof(H2.DataTypes.Enum))
                    size += ((H2.DataTypes.Enum)f.GetValue(Tag)).ByteCountLength;
                else
                    throw new Exception("Unknown Type");
            }

            return size;
        }

        public int GetUnusedID()
        {
            int i = TagInfoCache[0].ID;
            foreach(TagInfo ti in TagInfoCache)
                if(ti.ID > i) i = ti.ID;
            i += 65537;
            return i;
        }

        /// <summary>
        /// Adds A Tags Meta To The Map And Adds Its Info To The FileTable
        /// </summary>
        /// <param name="MetaData"></param>
        /// <param name="Path"></param>
        /// <param name="Class"></param>
        public void Add(TagContainer tagCon, string Class, string Path)
        {
            object Tag = tagCon.Tag;
            object Raw = tagCon.RawData;

            TagInfo ti = new TagInfo();

            int TagInfoSize = 16;
            int PathSize = Path.Length + 1;
            int FileIndexSize = 4;
            int TagSize = GetSize(Tag, true);
            int BitmapRawSize = 0;
            int MetaSize = 0;
            int MetaStart = Map.Header.MetaStart;
            for (int i = 0; i < TagInfoCache.Count; i++)
            {
                int MetaSizeCheck = TagInfoCache[i].Offset + TagInfoCache[i].Size - MetaStart;
                if (MetaSizeCheck > MetaSize) MetaSize = MetaSizeCheck;
            }

            int FTS = Map.Header.FileTableSize;
            int TotalFileTablePadding = Map.CalculatePaddingSize(FTS, 512);
            int FileTablePaddingSurplus = Map.CalculatePaddingSize(FTS + PathSize, 512) - TotalFileTablePadding;
            TotalFileTablePadding += FileTablePaddingSurplus;

            int FITS = Map.Header.FileTableCount * FileIndexSize;
            int TotalFITSPadding = Map.CalculatePaddingSize(FITS, 512);
            int FITSPaddingSurplus = Map.CalculatePaddingSize(FITS + FileIndexSize, 512) - TotalFITSPadding;
            TotalFITSPadding += FITSPaddingSurplus;

            int TotalMetaPadding = Map.CalculatePaddingSize(MetaSize, 512);
            int MetaPaddingSurplus = Map.CalculatePaddingSize(MetaSize + TagSize, 512) - TotalMetaPadding;
            TotalMetaPadding += MetaPaddingSurplus;


            BitmapCollection.BitmapDataChunk[] bitmRaw = null;
            if (Tag.GetType() == typeof(H2.TagStructures.bitm))
            {
                bitmRaw = (BitmapCollection.BitmapDataChunk[])Raw;
                for (int i = 0; i < bitmRaw.Length; i++)
                    BitmapRawSize += bitmRaw[i].LOD1.Length + bitmRaw[i].LOD2.Length + bitmRaw[i].LOD3.Length;
            }
            int Shifter = PathSize + FileIndexSize + FileTablePaddingSurplus + FITSPaddingSurplus;

            ti.Size = TagSize;
            ti.ID = GetUnusedID();

            //Shift Bitmap Raw
            #region Bitmap
            TagInfo[] BitmapTagInfos = Map.Tags.GetTagInfoArrayOfClass("bitm");
            string[] BitmapFileNames = Map.Tags.GetStringArrayOfClass("bitm");

            int BitmapPaddingSize = 0;

            Map.Status = "Fixing Bitmap Raw...";
            for (int i = 0; i < BitmapTagInfos.Length; i++)
            {
                System.Windows.Forms.Application.DoEvents();

                Map.Position = BitmapTagInfos[i].Offset + 68;

                int ChunkCount = Map.ReadInt32();
                int Pointer = Map.ReadInt32() - Map.SecondaryMagic;
                for (int x = 0; x < ChunkCount; x++)
                {
                    int ChunkStartOffset = Pointer + (116 * x);
                    Map.Position = ChunkStartOffset + 28;

                    int LOD1Offset = Map.ReadInt32();
                    if (LOD1Offset == -1) break;
                    else if (((long)LOD1Offset & 0XC0000000) == 0)
                    {
                        int LOD2Offset = Map.ReadInt32();
                        int LOD3Offset = Map.ReadInt32();

                        Map.Position = ChunkStartOffset + 28;
                        Map.Write(LOD1Offset + Shifter);
                        if (LOD2Offset != -1) Map.Write(LOD2Offset + Shifter);
                        else Map.Position += 4;
                        if (LOD3Offset != -1) Map.Write(LOD3Offset + Shifter);
                    }
                }
            }

            int StartOffset = Map.IndexOffset;
            if (Tag.GetType() == typeof(H2.TagStructures.bitm))
            {
                H2.TagStructures.bitm bitmTag = (H2.TagStructures.bitm)Tag;
                int MovingOffset = StartOffset + Shifter;
                int[] PaddingSizes = new int[bitmTag._68_Bitmap_Data.Length];
                for (int x = 0; x < bitmTag._68_Bitmap_Data.Length; x++)
                {
                    int LOD1Offset = bitmTag._68_Bitmap_Data[x]._28_LOD1_Offset;
                    if (LOD1Offset == -1) break;
                    else if (((long)LOD1Offset & 0XC0000000) == 0)//Is Internal
                    {
                        if (bitmRaw[x].LOD1.Length > 0)
                        {
                            bitmTag._68_Bitmap_Data[x]._28_LOD1_Offset = MovingOffset;
                            MovingOffset += bitmRaw[x].LOD1.Length;
                        }
                        if (bitmRaw[x].LOD2.Length > 0)
                        {
                            bitmTag._68_Bitmap_Data[x]._32_LOD2_Offset = MovingOffset;
                            MovingOffset += bitmRaw[x].LOD2.Length;
                        }
                        if (bitmRaw[x].LOD3.Length > 0)
                        {
                            bitmTag._68_Bitmap_Data[x]._36_LOD3_Offset = MovingOffset;
                            MovingOffset += bitmRaw[x].LOD3.Length;
                        }

                        PaddingSizes[x] = Map.CalculatePaddingSize(bitmRaw[x].LOD1.Length + bitmRaw[x].LOD2.Length + bitmRaw[x].LOD3.Length, 512);
                        BitmapPaddingSize += PaddingSizes[x];
                        MovingOffset += PaddingSizes[x];
                    }
                }

                //Add Data
                Map.BaseStream.SetLength(Map.Length + BitmapRawSize + BitmapPaddingSize);
                Map.WriteAt(StartOffset + BitmapRawSize + BitmapPaddingSize, Map.ReadBytesAt(StartOffset, (int)Map.Length - StartOffset, true), true);
                Map.Header.IndexOffset += BitmapRawSize + BitmapPaddingSize;
                Map.Position = StartOffset;

                for (int x = 0; x < bitmRaw.Length; x++)
                {
                    Map.Write(bitmRaw[x].LOD1);
                    Map.Write(bitmRaw[x].LOD2);
                    Map.Write(bitmRaw[x].LOD3);
                    Map.Write(new byte[PaddingSizes[x]]);
                }
            }
            #endregion

            ti.ChangeOffset(MetaStart + MetaSize, Map.SecondaryMagic + TagInfoSize + PathSize + FileIndexSize + BitmapRawSize + BitmapPaddingSize);

            //Add Data
            Map.BaseStream.SetLength(Map.Length + TagInfoSize + PathSize + FileIndexSize + TagSize + FileTablePaddingSurplus + FITSPaddingSurplus + MetaPaddingSurplus);

            //Update Sizes
            Map.Header.Filesize += PathSize + FileIndexSize + TagInfoSize + TagSize + BitmapRawSize + BitmapPaddingSize;
            Map.Header.NonRawSize += PathSize + FileIndexSize + TagInfoSize + TagSize;
            Map.Header.MetaSize += TagSize;

            Map.Status = "Fixing Crazy...";
            //Shift Crazy
            Map.Header.StrangeFileStringsOffset += Shifter;

            Map.Status = "Fixing Unicode...";
            //Shift Unicode Index/Table
            #region Unicode
            Map.Unicode.English.StringIndexOffset += Shifter;
            Map.Unicode.English.StringTableOffset += Shifter;
            Map.Unicode.Chinese.StringIndexOffset += Shifter;
            Map.Unicode.Chinese.StringTableOffset += Shifter;
            Map.Unicode.Dutch.StringIndexOffset += Shifter;
            Map.Unicode.Dutch.StringTableOffset += Shifter;
            Map.Unicode.French.StringIndexOffset += Shifter;
            Map.Unicode.French.StringTableOffset += Shifter;
            Map.Unicode.Italian.StringIndexOffset += Shifter;
            Map.Unicode.Italian.StringTableOffset += Shifter;
            Map.Unicode.Japanese.StringIndexOffset += Shifter;
            Map.Unicode.Japanese.StringTableOffset += Shifter;
            Map.Unicode.Korean.StringIndexOffset += Shifter;
            Map.Unicode.Korean.StringTableOffset += Shifter;
            Map.Unicode.Portuguese.StringIndexOffset += Shifter;
            Map.Unicode.Portuguese.StringTableOffset += Shifter;
            Map.Unicode.Spanish.StringIndexOffset += Shifter;
            Map.Unicode.Spanish.StringTableOffset += Shifter;
            #endregion

            Map.Status = "Moving Data...";
            //Move Data
            StartOffset = Map.Index.TagInfoIndexOffset + (TagInfoSize * (Map.Index.TagCount - 1));//Go To Start Of Ugh! Info So It Remains As The Last Item
            Map.WriteAt(StartOffset + TagInfoSize, Map.ReadBytesAt(StartOffset, (int)Map.Length - StartOffset, true), true);

            Map.Status = "Writing Tag Info...";
            //Add Tag Info
            Map.Position = StartOffset;//Go To End Of Tag Info Table, (-1 to go before ugh!)
            Map.WriteReverseString(Class);
            Map.Write(ti.ID);
            Map.Write(ti.RawMetaOffset);
            Map.Write(ti.Size);

            Map.Status = "Moving Data...";
            //Move Index (Inc TagInfo), Bitmap Raw, Crazy, Unicode, Last File Index (Ugh!)
            int FileTableIndexEnd = Map.Header.FileTableIndexOffset + ((Map.Header.FileTableCount - 1) * FileIndexSize);
            Map.WriteAt(FileTableIndexEnd + FileIndexSize + FITSPaddingSurplus, Map.ReadBytesAt(FileTableIndexEnd, (int)Map.Length - FileTableIndexEnd, true), true);

            Map.Status = "Fixing Index...";
            //Shift Index
            Map.Header.IndexOffset += FileIndexSize + FITSPaddingSurplus;
            Map.Header.IndexSize += TagInfoSize;
            Map.Index.TagCount += 1;

            //Move File Index + Ugh Path
            string ugh = PathCache[ClassCache.Count - 1];
            StartOffset = Map.Header.FileTableOffset + Map.Header.FileTableSize - (ugh.Length + 1);
            Map.WriteAt(StartOffset + PathSize + FileTablePaddingSurplus, Map.ReadBytesAt(StartOffset, (int)Map.Length - StartOffset, true), true);
            Map.Header.FileTableIndexOffset += PathSize + FileTablePaddingSurplus;
            Map.Header.IndexOffset += PathSize + FileTablePaddingSurplus;

            Map.Status = "Writing File Path...";
            //Add To File Table
            Map.Position = StartOffset;
            int IndexPointer = (int)Map.Position - Map.Header.FileTableOffset;
            Map.Write(Path + '\0');
            Map.Position += ugh.Length + 1;
            Map.Write(new byte[TotalFileTablePadding]);
            Map.Header.FileTableSize += PathSize;

            Map.Status = "Writing File Index...";
            //Write File Index
            Map.Position = Map.Header.FileTableIndexOffset + ((Map.Header.FileTableCount - 1) * FileIndexSize);
            Map.Write(IndexPointer);
            Map.Write(IndexPointer + PathSize);
            Map.Write(new byte[TotalFITSPadding]);
            Map.Header.FileTableCount += 1;

            //Update Cache
            Map.Flush();
            Map.Reload();

            Map.Status = "Writing Tag...";
            //Write Tag
            StartOffset = Map.Tags.TagInfoCache[Map.Tags.TagInfoCache.Count - 2].Offset;
            Map.Position = StartOffset;
            int EofMeta = StartOffset + GetSize(Tag, false);
            Map.Write(Tag.GetType(), Tag, Map.Index.SecondaryMagic, ref EofMeta);
            Map.Position = EofMeta;
            Map.Write(new byte[TotalMetaPadding]);

            Map.Reload();

            Map.Status = "Tag Added Successfully.";
        }

        /// <summary>
        /// Adds A Tags Meta To The Map And Adds Its Info To The FileTable
        /// </summary>
        /// <param name="MetaData"></param>
        /// <param name="RawData"></param>
        /// <param name="Path"></param>
        /// <param name="Class"></param>
        public void Add(byte[] MetaData, byte[] RawData, string Path, string Class)
        {
            throw new Exception("The method or operation is not implemented.");

            //Add To File Table
            //Add To File Index
            //Shift Unicode Index/Table
            //Shift Crazy
            //Shift Bitmap Raw
            //Shift Index
            //Shift Meta

            //Update Cache
        }

        /// <summary>
        /// Remove All Tags And Associated Data From The Map
        /// </summary>
        public void Clear()
        {
            throw new Exception("The method or operation is not implemented.");
            //Empty File Table
            //Empty File Index
            //Empty Unicode Index/Table
            //Shift/Empty Crazy
            //Empty Bitmap Raw
            //Shift Index
            //Empty Meta

            //Update Cache
        }

        /// <summary>
        /// Returns True If A Tag With Provided Path And Class Is Found
        /// </summary>
        /// <param name="Path"></param>
        /// <param name="Class"></param>
        /// <returns></returns>
        public bool Contains(string Class, string Path)
        {
            for (int i = 0; i < PathCache.Count; i++)
            {
                if (PathCache[i] == Path)
                {
                    if (ClassCache[i] == Class)
                        return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Returns Index Of Tag With Provided Path And Class If Found Else Returns -1
        /// </summary>
        /// <param name="Path"></param>
        /// <param name="Class"></param>
        /// <returns></returns>
        public int IndexOf(string Class, string Path)
        {
            for (int i = 0; i < PathCache.Count; i++)
            {
                if (PathCache[i] == Path)
                {
                    if (ClassCache[i] == Class)
                        return i;
                }
            }

            return -1;
        }

        /// <summary>
        /// Get Tag Count
        /// </summary>
        public int Count
        {
            get { return Map.Index.TagCount; }
        }

        /// <summary>
        /// Remove Tag From Map
        /// </summary>
        /// <param name="Path"></param>
        /// <param name="Class"></param>
        /// <returns></returns>
        public bool Remove(string Path, string Class)
        {
            throw new Exception("The method or operation is not implemented.");

            //Remove From File Table
            //Remove From File Index
            //Shift Unicode Index/Table
            //Shift Crazy
            //Shift Bitmap Raw
            //Shift Index
            //Shift Meta

            //Update Cache
        }
    }
}
